/*
 *  Getris -- A Tetris game written in Groovy.
 *  Copyright (C) 2008 Johnny Jian
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package cn.johnnyjian.getris.game

import cn.johnnyjian.getris.game.quadrel.*

class Container extends Observable {

	public static final QUADRELS = [
	    QuadrelOne, QuadrelTwo, QuadrelThree, QuadrelFour, QuadrelFive, QuadrelSix, QuadrelSeven
	]

	def width
	def height
	def queueSize
	def queue
	def squares
	def droppingQuadrel
	def quadrel
	def gameOver
	def removedLineCount

	Container(width, height, queueSize) {
		this.width = width
		this.height = height
		this.queueSize = queueSize

		queue = new LinkedList()
		squares = new boolean[height][width]
		droppingQuadrel = new DroppingQuadrel(this)
		gameOver = false
		removedLineCount = 0

		fillQueue()
	}

	private fillQueue() {
		while (queue.size() < queueSize) {
			queue.offer randomQuadrel()
		}
	}

	private randomQuadrel() {
		def rand = new Random()
		def index = rand.nextInt(QUADRELS.size())
		QUADRELS[index].newInstance()
	}

	void notifyObservers() {
		setChanged()
		super.notifyObservers()
	}

	def startToDrop() {
		dropNextQuadrel()
	}

	def dropNextQuadrel() {
		quadrel = queue.poll()
		fillQueue()

		// set the quadrel to the middle of the width, and above the container
		droppingQuadrel.x = QuadrelUtils.centerHorizontal(quadrel, width)
		droppingQuadrel.y = -quadrel.bound

		droppingQuadrel.reachedBottom = false

		// drop the quadrel until reaching the top
		while (droppingQuadrel.top < 0 && !droppingQuadrel.reachedBottom) {
			doDropPaceQuadrel()
		}

		// if the dropping quadrel has reached the bottom, then its top should be less than 0
		// and so the game is over
		if (droppingQuadrel.reachedBottom) {
			gameOver = true
		}

		notifyObservers()
	}

	def removeFullLines() {
		// find all full lines
		def fullLines = (droppingQuadrel.bottom..droppingQuadrel.top).findAll { line ->
			squares[line].every { it }
		}
		removedLineCount = fullLines.size()

		// do nothing if there are no full lines
		if (!fullLines) {
			return
		}

		// append a sentinel
		fullLines << -(removedLineCount + 1)

		// move the squares down to fill the spaces after removing the full lines
		fullLines[0..-2].eachWithIndex { l, i ->
			((l - 1)..<fullLines[i + 1]).each { line ->
				moveLine line, line + i + 1
			}
		}

		notifyObservers()
	}

	protected rotateQuadrel() {
		// check if the quadrel can be rotated
		if (cannotRotate()) {
			return
		}

		moveQuadrelWith { quadrel.rotate() }

		notifyObservers()
	}

	private cannotRotate() {
		def rotated = quadrel.preRotate()
		if (droppingQuadrel.y + rotated.top < 0) {
			return true
		}
		def result = false
		rotated.eachSquare { x, y ->
			if (result) {
				return
			}
			def xb = droppingQuadrel.x + x
			def yb = droppingQuadrel.y + y
			if (!droppingQuadrel.isSquareAt(xb, yb) && isSquareAt(xb, yb)) {
				result = true
			}
		}
		return result
	}

	def moveLeftQuadrel() {
		if (cannotMove(-1, 0)) {
			return
		}

		moveQuadrelWith { --droppingQuadrel.x }

		notifyObservers()
	}

	protected moveRightQuadrel() {
		if (cannotMove(1, 0)) {
			return
		}

		moveQuadrelWith { ++droppingQuadrel.x }

		notifyObservers()
	}

	private doDropPaceQuadrel() {
		// check if reaching the bottom
		if (cannotMove(0, 1)) {
			droppingQuadrel.reachedBottom = true
			return
		}

		moveQuadrelWith { ++droppingQuadrel.y }
	}

	protected dropPaceQuadrel() {
		doDropPaceQuadrel()

		def changed = !droppingQuadrel.reachedBottom
		if (changed) {
			notifyObservers()
		}
	}

	private cannotMove(dx, dy) {
		def result = false
		droppingQuadrel.eachSquare { x, y ->
			if (result) {
				return
			}
			if (!droppingQuadrel.isSquareAt(x + dx, y + dy) && isSquareAt(x + dx, y + dy)) {
				result = true
			}
		}
		return result
	}

	protected dropDownQuadrel() {
		def changed = false

		moveQuadrelWith {
			// find the minimum distance between the quadrel and the bottom
			def minDistance = Integer.MAX_VALUE
			droppingQuadrel.eachSquare { x, y ->
				def distance = 0
				while (!isSquareAt(x, y + distance + 1)) {
					++distance
				}
				minDistance = Math.min(minDistance, distance)
			}
			droppingQuadrel.y += minDistance

			if (minDistance > 0) {
				changed = true
			}
		}
		droppingQuadrel.reachedBottom = true

		if (changed) {
			notifyObservers()
		}
	}

	private moveQuadrelWith(Closure closure) {
		quadrel.removeFrom this, droppingQuadrel.x, droppingQuadrel.y
		closure()
		quadrel.copyTo this, droppingQuadrel.x, droppingQuadrel.y
	}

	def setSquareAt(x, y, square) {
		if (x in 0..<width && y in 0..<height) {
			squares[y][x] = square
		}
	}

	def isSquareAt(x, y) {
		if (x in 0..<width && y in 0..<height) {
			return squares[y][x]
		} else {
			def above = y < 0 && x in 0..<width
			return !above // assume the spaces above the container are all empty
		}
	}

	def moveLine(source, target) {
		if (source < 0) {
			squares[target] = new boolean[width]
		} else {
			squares[target] = squares[source]
		}
	}

}

/**
 * This class is just for semantic. For example, the user can do the operation like this:
 *     container.droppingQuadrel.moveLeft()
 *
 * The following methods can be called on this class:
 *     moveLeft()
 *     moveRight()
 *     rotate()
 *     dropPace()
 *     dropDown()
 */
class DroppingQuadrel {

	/**
	 * These are the methods can be called on this class
	 */
	private static final METHODS = ['moveLeft', 'moveRight', 'rotate', 'dropPace', 'dropDown'] as Set

	def delegate
	def x
	def y
	def reachedBottom

	DroppingQuadrel(container) {
		delegate = container
	}

	def methodMissing(String name, args) {
		if (name in METHODS) {
			return delegate."${name}Quadrel"()
		} else {
			throw new MissingMethodException(name, getClass(), args)
		}
	}

	def propertyMissing(String name) {
		delegate.quadrel."$name"
	}

	def getLeft() {
		x + delegate.quadrel.left
	}

	def getRight() {
		x + delegate.quadrel.right
	}

	def getTop() {
		y + delegate.quadrel.top
	}

	def getBottom() {
		y + delegate.quadrel.bottom
	}

	def eachSquare(Closure closure) {
		delegate.quadrel.eachSquare { xb, yb ->
			closure(x + xb, y + yb)
		}
	}

	def isSquareAt(xb, yb) {
		delegate.quadrel.isSquareAt(xb - x, yb - y)
	}

}
